﻿using Google.ProtocolBuffers;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Threading.Tasks;
using System.Net;
using System.Linq;
using System.Windows;
using System.Threading;

namespace AuroraPlatform.TCP
{
    public class TCPClient
    {
        private static int _sendNumber;
        private static TCPClient _singleton;
        private TcpChannel _tcpChannel;
        private ProtocolDecoder _decoder;
        private Dictionary<int, TaskCompletionSource<byte[]>> _callDictionary;

        private ProtocolDecoder Decoder
        {
            get
            {
                if (_decoder == null)
                {
                    _decoder = new ProtocolDecoder();
                    _decoder.DecodedCompletely += (sender, e) =>
                    {
                        ProtocolData protocol = e.Protocol;
                        TaskCompletionSource<byte[]> tcs = null;
                        if (_callDictionary.TryGetValue(protocol.SerialNumber, out tcs))
                        {
                            _callDictionary.Remove(protocol.SerialNumber);
                            tcs.SetResult(protocol.Data);
                        }
                    };
                }
                return _decoder;
            }
        }

        public static TCPClient Current
        {
            get
            {
                if (_singleton == null)
                {
                    _singleton = new TCPClient();
                    
                }
                return _singleton;
            }
        }

        public static EndPoint RemoteEndPoint
        {
            get;
            set;
        }

        private bool IsConnected
        {
            get
            {
                return this._tcpChannel == null ? false : this._tcpChannel.State == TcpChannelState.Connected;
            }
        }

        private TCPClient()
        {
            _callDictionary = new Dictionary<int, TaskCompletionSource<byte[]>>();
        }

        public async Task<bool> ConnectAsync()
        {
            if (this._tcpChannel == null || this._tcpChannel.State != TcpChannelState.Disconnected)
            {
                this._tcpChannel = new TcpChannel();
                this._tcpChannel.Received += this.TcpChannelReceived;
                this._tcpChannel.Error += this.TcpChannelError;
            }

            return await this._tcpChannel.ConnectAsync(RemoteEndPoint);
        }

        private void TcpChannelReceived(object sender, TcpChannelReceiveEventArgs e)
        {
            Decoder.Push(e.Data);
        }

        private void TcpChannelError(object sender, TcpChannelErrorEventArgs e)
        {
            Debug.WriteLine(e.Exception.Message);

            Deployment.Current.Dispatcher.BeginInvoke(async () =>
            {
                if (this._tcpChannel != null)
                {
                    this._tcpChannel.Received -= this.TcpChannelReceived;
                    this._tcpChannel.Error -= this.TcpChannelError;
                    this._tcpChannel.Close();
                    this._tcpChannel = null;
                }
            });
        }

        public async Task<byte[]> Call(int commandID, byte[] request)
        {
            int number = SendRequest(commandID, request);
            var tcs = new TaskCompletionSource<byte[]>();

            _callDictionary[number] = tcs;

            return await tcs.Task;
        }

        public void Reset()
        {
            if (this._tcpChannel != null)
            {
                this._tcpChannel.Received -= this.TcpChannelReceived;
                this._tcpChannel.Error -= this.TcpChannelError;
                this._tcpChannel.Close();
            }
            _singleton = null;
        }

        private int SendRequest(int commandID, byte[] request)
        {
            if (request == null)
            {
                request = new byte[0];
            }

            ProtocolData protocol = new ProtocolData()
            {
                CommandID = commandID,
                SerialNumber = ++_sendNumber,
                Data = request
            };

            ProtocolEncoder encoder = new ProtocolEncoder();
            _tcpChannel.Send(encoder.Encode(protocol));
            return _sendNumber;
        }
    }
}
